package com.netty.bfport.netty.websocket;

import com.netty.bfport.netty.server.NettyServerHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * @Author: zhaoyang
 * @Data: 2022/5/1 11:29
 * @Description
 */
@Component
@Slf4j
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
    private WebSocketServerHandshaker handshaker;
    public static Integer port;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        // http请求，建立握手连接
        if (msg instanceof FullHttpRequest) {
            NettyServerHandler.channelsWeb.add(ctx.channel());
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 关闭链路的控制消息
        if (frame instanceof CloseWebSocketFrame) {
            System.out.println("收到关闭请求");
            Channel channel = NettyServerHandler.channelsWeb.find(ctx.channel().id());
            NettyServerHandler.channelsWeb.remove(channel);
            handshaker.close(ctx.channel(), ((CloseWebSocketFrame) frame).retain());
            return;
        }
        // 维持链路的ping消息
        if (frame instanceof PingWebSocketFrame) {
            System.out.println("收到ping");
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 通信双方只使用文本
        if (!(frame instanceof TextWebSocketFrame)) {
            throw new UnsupportedOperationException(String.format(
                    "%s frame types not supported", frame.getClass().getName()));
        }

        // 客户端发过了的消息
        String request = ((TextWebSocketFrame) frame).text();
        // 服务端返回客户端发过了的消息 + 当前时间
        ctx.channel().write(new TextWebSocketFrame(request
                + " , 欢迎使用Netty WebSocket服务，现在时刻：" + new java.util.Date().toString()));

    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        // 根据请求头Upgrade字段判断是不是websocket
        if (!req.decoderResult().isSuccess() || !"websocket".equals(req.headers().get("Upgrade"))) {
            // 400
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
            return;
        }
        // 握手工厂
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                "ws://localhost:"+port+"/websocket", null, false);
        // 将websocket相关的编/解码类动态添加到ChannelPipeline
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            System.out.println("进行握手");
            // 握手
            handshaker.handshake(ctx.channel(), req);
        }
    }


    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        if (res.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            HttpUtil.setContentLength(res, res.content().readableBytes());
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

